今天的目標是設計一個提供 GraphQL Query 查詢人類圖的介面。
試著讓每個 field 都有 resolver,如果 Query 不是完整的人類圖時,則可減少運算。
首先從 BodyGraphResolver
開始:
@Resolver(() => BodyGraphDto)
export class BodyGraphResolver {
public constructor(
private readonly bodyGraphService: BodyGraphService,
) {}
}
在 BodyGraph
> Imprint
> Substructure
這樣的結構中,BodyGraph
提供出生時間,Imprint
提供出生時的尤利安曆時間,Substructure
提供天球位置,以計算閘門和爻線。
在 BodyGraphResolver
中,首先先定義一個 Query:
@Query(() => BodyGraphDto, { name: 'bodyGraph' })
public getBodyGraph(@Args() birth: DateTimeDto): Pick<BodyGraphDto, 'birth'> {
return { birth };
}
BodyGraphResolver
中的 FieldResolver 提供的是 Imprint
,因此從上級 BodyGraph
中取得出生時間,換算成尤利安曆時間。
@ResolveField('design', () => ImprintDto)
public getDesign(
@Parent() bodyGraph: Pick<BodyGraphDto, 'birth'>,
): Pick<ImprintDto, 'jd'> {
const jd = this.bodyGraphService.getDesignDate(bodyGraph.birth);
return { jd };
}
@ResolveField('personality', () => ImprintDto)
public getPersonality(
@Parent() bodyGraph: Pick<BodyGraphDto, 'birth'>,
): Pick<ImprintDto, 'jd'> {
const jd = this.bodyGraphService.getPersonalityDate(bodyGraph.birth);
return { jd };
}
先定義 ImprintResolver
:
@Resolver(() => ImprintDto)
export class ImprintResolver {
public constructor(private readonly bodyGraphService: BodyGraphService) {}
}
順便將 ResolveField 裝飾器先綁定第二個參數,以簡化後續程式碼:
const ResolveSubstructure = (name: string): MethodDecorator =>
ResolveField(name, () => SubstructureDto);
這邊對前幾天 BodyGraphService 的一些函數做了整理,首先新增 getPosition()
:
public getPosition(jd: number, ipl: BodyGraphPlanet): number {
const calcResult = swe_calc_ut(jd, ipl, this.SWE_IFLAG);
if (!('longitude' in calcResult)) {
this.assertSwissephResult(calcResult);
throw new Error('invalid result of swe_calc_ut()');
}
return calcResult.longitude;
}
並且調整了 getOppositePosition()
的參數:
public getOppositePosition(jd: number, ipl: BodyGraphPlanet): number {
return swe_degnorm(this.getPosition(jd, ipl) + 180).x360;
}
未來可以考慮對
getPosition()
做 memoize。
如果希望個別 request 分開暫存,memoize storage 可以考慮用 ALS (Async Local Storage)。
實作十三個星球的 FieldResolver,分別利用尤利安時間計算星球位置。
@ResolveSubstructure('sun')
public getSubstructureSun(
@Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
return {
longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.SUN),
};
}
@ResolveSubstructure('earth')
public getSubstructureEarth(
@Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
return {
longitude: this.bodyGraphService.getOppositePosition(
jd,
BodyGraphPlanet.SUN,
),
};
}
@ResolveSubstructure('moon')
public getSubstructureMoon(
@Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
return {
longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.MOON),
};
}
@ResolveSubstructure('northNode')
public getSubstructureNorthNode(
@Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
return {
longitude: this.bodyGraphService.getPosition(
jd,
BodyGraphPlanet.TRUE_NODE,
),
};
}
@ResolveSubstructure('southNode')
public getSubstructureSouthNode(
@Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
return {
longitude: this.bodyGraphService.getOppositePosition(
jd,
BodyGraphPlanet.TRUE_NODE,
),
};
}
@ResolveSubstructure('mercury')
public getSubstructureMercury(
@Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
return {
longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.MERCURY),
};
}
@ResolveSubstructure('venus')
public getSubstructureVenus(
@Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
return {
longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.VENUS),
};
}
@ResolveSubstructure('mars')
public getSubstructureMars(
@Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
return {
longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.MARS),
};
}
@ResolveSubstructure('jupiter')
public getSubstructureJupiter(
@Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
return {
longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.JUPITER),
};
}
@ResolveSubstructure('saturn')
public getSubstructureSaturn(
@Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
return {
longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.SATURN),
};
}
@ResolveSubstructure('uranus')
public getSubstructureUranus(
@Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
return {
longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.URANUS),
};
}
@ResolveSubstructure('neptune')
public getSubstructureNeptune(
@Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
return {
longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.NEPTUNE),
};
}
@ResolveSubstructure('pluto')
public getSubstructurePluto(
@Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
return {
longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.PLUTO),
};
}
定義 SubstructureResolver
:
@Resolver(() => SubstructureDto)
export class SubstructureResolver {
public constructor(private readonly bodyGraphService: BodyGraphService) {}
}
在 BodyGraphService
上新增兩個函數 getGate()
和 getLine()
:
public getGate(longitude: number): string {
const { x360 } = swe_degnorm(longitude + 58);
const gateIndex = Math.trunc(x360 / this.NUM_DEGS_PER_GATE);
return this.INDEXED_GATES[gateIndex];
}
public getLine(longitude: number): string {
const { x360 } = swe_degnorm(longitude + 58);
const remain = x360 % this.NUM_DEGS_PER_GATE;
const lineIndex = Math.trunc(remain / this.NUM_DEGS_PER_LINE) + 1;
return String(lineIndex);
}
const { x360 } = swe_degnorm(longitude + 58);
這一行在兩個函數重複計算了,之後可以獨立抽出來成一個函數,並做 memoize。
最後實作 gate 和 line 的 FieldResolver:
@ResolveField('gate')
public getGate(
@Parent() { longitude }: Pick<SubstructureDto, 'longitude'>,
): string {
return this.bodyGraphService.getGate(longitude);
}
@ResolveField('line')
public getLine(
@Parent() { longitude }: Pick<SubstructureDto, 'longitude'>,
): string {
return this.bodyGraphService.getLine(longitude);
}
打開 Playground 看看成果:
晚安,瑪卡巴卡。